跳到主要内容

SpringMVC 基本执行流程

概述

参考资料 官方文档

注意,这篇主要介绍的传统的 SpringMVC 流程方面的东西,请求响应相关看后一篇

SSM:Spring + SpringMVC + MyBatis MVC三层架构:

配置环境

直接导入 spring-webmvc 的包就能连带 Spring 一起导进来了

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>

Servlet 配置方式

令一个类去继承这个 HttpServlet 就行了 然后再重写它的两个方法 doGetdoPost

public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}

然后在 web.xml 里面配置这个 Mapper

<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.alsritter.servlet.HelloServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>

SpringMVC 的执行流程

Snipaste_2020-03-15_17-35-48.png

注:到了 Spring-Boot 就无需配置视图解析器了,因为前后端分离之后都是靠后端发 json 给前端

DispatcherServlet(调度服务):实际上配置 SpringMVC 就是一个 超级 Servlet;所有的请求都交给它来处理;

HandlerAdapter:就是一个适配器

ViewResolver(视图解析器):DispatcherServlet 给它提供 ModelAndView,然后进行了下列操作

  • 获取了 ModelAndView 的数据
  • 解析 ModelAndView 的视图名字
  • 拼接视图名字,找到对应的视图(例如下文的/WEB-INF/jsp/hello.jsp
  • 将数据渲染到这个视图上

但是上述的各种层级调用流程里面,用户只需要去实现 Handler 就行了,其他的层级不需要用户去处理,交由 SpringMVC 自己去处理

所以:SpringMVC 必须配置的三大件:处理器映射器处理器适配器视图解析器(前面两个可以直接使用<mvc:default-servlet-handler/>自动配置)

配置 web.xml 文件

参考资料 javaschool

下面是 SpringMVC 在 web.xml 里的配置文件。

注意,因为是 Spring 框架,所以还是需要添加配置文件,这里使用 <init-param> 来配置 Servlet 的初始化参数

<!--注册DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--关联一个springmvc的配置文件:【servlet-name】-servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!--启动级别-1-->
<load-on-startup>1</load-on-startup>
</servlet>

<!--/ 匹配所有的请求,不包括静态资源(但是如果没有配置其他的 servlet-mapping 则还是会交给这个默认的管理)-->
<!--/* 匹配所有的请求(只拦截一级),包括静态资源-->
<!--/** 匹配所有的请求,包括静态资源-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!--可见 SpringMVC 的本质就是把所有的请求映射交给 DispatcherServlet 来处理-->
<url-pattern>/</url-pattern>
</servlet-mapping>

实际上就是在 web.xml 里把所有的请求响应都交给 SpringMVC 这个 DispatcherServlet 来处理,所以一般设置 <url-pattern>/ 或者 /**(两个区别看注释)

补充下 web.xml 初始化标签 <init-param> 的用法

<init-param>
<param-name>key</param-name>
<param-value>val</param-value>
</init-param>

按照以上格式可以配置多个初始化参数.

在web.xml的 <servlet> 里面配置 Servlet 的初始化参数,并且必须放到 load-on-startup(启动级别)的前面

只要继承了 HttpServlet 类就能直接调用父类的这些方法来获取参数了

getInitParameter(String name)    //获取初始化参数值
getInitParameterNames() //以String对象的Enumeration的形式返回 servlet的初始化参数的名称
getServletName() //返回此 servlet 实例的名称

编写一个 Controller

SpringMVC 不同于传统的 Servlet 流程那样去继承一个 HttpServlet 类,而是去实现一个 Controller 接口去处理 ModelAndView

public class HelloController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 创建一个ModelAndView对象
ModelAndView mv = new ModelAndView();
// 封装ModelAndView,放在mv中
mv.addObject("msg", "HelloSpringMVC");
mv.setViewName("hello");
return mv;
}
}

配置 SpringConfig

在配置文件里添加 HandlerMapping、HandlerAdapter、视图映射器 这几个处理器的 Bean,以及把前面写的 Controller 加进来

<!--处理器映射器:显示配置,一般后面开发都会使用注解替代掉,下面的适配器同理-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>

<!--视图解析器:DispatcherServlet(注意,这里是必须写的,不过可以使用别的模板引擎替换它)-->
<!--这里设置 id 的目的是自动装配-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!--确立建设 URL 时被预置到视图名称的前缀(注意最后一定要加上一个 / 因为这玩意就是无情的字符拼接)-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--确立建设 URL 时被附加到视图名称的后缀。-->
<property name="suffix" value=".jsp"/>
</bean>

<!--再把写的 HelloController 加进来(注意已经写好了 hello.jsp 了)-->
<!--这里的 id 表示匹配 /hello 如果匹配到就丢进来-->
<bean id="/hello" class="com.alsritter.controller.HelloController"/>

注解配置 Controller

首先在 Spring 配置文件里启用注解支持(具体看注解)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!--自动扫描包,让指定包下的注解生效,由 IOC 容器统一管理-->
<context:component-scan base-package="com.alsritter.controller"/>


<!--
使用默认配置,会自动添加以下bean
DefaultServletHttpRequestHandler : 默认的Servlet请求处理器
SimpleUrlHandlerMapping : url - handler映射器
HttpRequestHandlerAdapter : 处理器适配器

注意:DispatcherServlet 也会拦截静态资源
这个默认的 DefaultServletHttpRequestHandler 可以让SpringMVC不处理静态资源的请求
-->
<mvc:default-servlet-handler/>


<!--
开启mvc注解驱动,在SpringMVC中一般采用 @RequestMapping 注解来完成映射关系
要想使 @RequestMapping 注解生效
必须向上下文中注册 DefaultAnnotationHandlerMapping 和一个 AnnotationMethodHandlerAdapter 实例
而 annotation-driven 配置可以自动注入上述两个实例
-->
<mvc:annotation-driven/>

<!--视图解析器:DispatcherServlet-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!--确立建设URL时被预置到视图名称的前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--确立建设URL时被附加到视图名称的后缀。-->
<property name="suffix" value=".jsp"/>
</bean>
</beans>

现在就可以直接使用注解来直接创建 Controller

@Controller
@RequestMapping("/helloController")
public class HelloController {

// 真实访问地址:项目名/helloController/hello
@RequestMapping("hello")
public String sysHello(Model model){
/*
补个java的小知识:
C# 传参有 ref 和 out 这种语法糖(ref关键字可使传递的形参与实参绑定起来),
但是 java 没有,不过实际上 java 中传入的形参是一个引用类型时效果和使用 ref 是差不多的,
因为形参本质就是一个封装的指针,所以只是操作了同个内存里的那个对象,
所以本质上通过形参对对象进行操作和直接操作一样。
因此也可以视为 java 隐式的 “形参与实参绑定起来”
*/
model.addAttribute("msg","hello,SpringMVC");
return "hello";
}
}

分层注解

MVC 几个注解(但是效果都和 @Component 一样),这些不同的注解只是用来标识不同层

  • @Component:就只是一个普通的组件
  • @Repository:dao层
  • @Service:service层
  • @Controller:controller层,被这个注解的类中的所有方法,如果返回值是 Spring,并且有具体页面可以跳转,那么就会被视图解析器解析;
  • @RestController:controller层注解,只不过这个注解是直接返回数据

@Repository 注解的作用

@Repository@Controller@Service@Component 的作用差不多,都是把对象交给 Spring管理。@Repository 用在持久层的接口上,这个注解是将接口的一个实现类交给 Spring 管理。

为什么有时候我们不用 @Repository 来注解接口,我们照样可以注入到这个接口的实现类呢?

  • Spring 配置文件中配置了 MapperScannerConfigurer 这个 Bean,它会扫描持久层接口创建实现类并交给 Spring 管理。
  • 接口上使用了 @Mapper 注解或者 SpringBoot 中主类上使用了 @MapperScan 注解,和 MapperScannerConfigurer 作用一样。

@Repository 的作用:该注解的作用不只是将类识别为 Bean,同时它还能将所标注的类中抛出的数据访问异常封装为 Spring 的数据访问异常类型。 Spring 本身提供了一个丰富的并且是与具体的数据访问技术无关的数据访问异常结构,用于封装不同的持久层框架抛出的异常,使得异常独立于底层的框架。

注意:有时 IDEA 报错找不到 Bean 时也可以通过加上这个注解来解决~

加上这个 @Repository 就不会警告了

@Repository("brandMapper")
public interface BrandMapper extends Mapper<Brand> {
}

重定向和转发

通过设置 ServletAPI 就能直接进行视图解析,配置了 Controller 后 Spring 会自动把 HttpServletResponse 对象注入进来,可以通过它来进行重定向和转发

@RequestMapping("test")
public String test(HttpServletRequest req, HttpServletResponse resp) {
// 这里测试发现可以直接使用ServletApi里的东西
HttpSession session = req.getSession();
System.out.println(session.getId());


// 注意前面的HttpServletRequest不影响这个return
// formard: 用来标注这个是转发 如果是 redirect: 则是重定向
return "formard:/WEB-INF/jsp/test.jsp";
}

但是上面直接使用 HttpServletResponse 进行重定向需要先 关掉视图解析器

注意:前面加上

formard::表示转发 redirect::表示重定向

Spring 静态资源的问题

上面配置的 DispatcherServlet 默认拦截了所有的请求,当外部访问静态资源时,也会经过这个 DispatcherServlet,但是这个 DispatcherServlet 拿到这个请求后会优先和内部的 Controller 进行匹配,找不到就返回 404 所以实际上并没有成功的访问到静态资源

所以需在 Spring-Config 配置文件里加上静态资源地址,使之访问静态资源的请求不进行 Controller 匹配

<!-- 匹配到以 static 开头的访问就是访问静态资源,不对其进行 Controller 匹配 -->
<!-- 这个 location 表示是哪个目录下的资源对外开放 -->
<mvc:resources mapping="/static/**" location="/static/"/>

或者直接交给 Tomcat 来进行管理(就无需配置上面的了)

<mvc:default-servlet-handle/>